6 changed files with 329 additions and 1 deletions
			
			
		- 
					1.gitignore
 - 
					5client/jumpcli.go
 - 
					297client/jumpcli.py
 - 
					1client/requirements.txt
 - 
					24examples/cmd-menu.py
 - 
					2server/manager_user.sh
 
@ -1,3 +1,4 @@ | 
			
		|||||
jumpserver | 
				jumpserver | 
			
		||||
.idea | 
				.idea | 
			
		||||
.idea/ | 
				.idea/ | 
			
		||||
 | 
				examples/ | 
			
		||||
@ -0,0 +1,297 @@ | 
			
		|||||
 | 
				#!/usr/bin/env python3 | 
			
		||||
 | 
				# -*- coding: UTF-8 -*- | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				import paramiko | 
			
		||||
 | 
				import os | 
			
		||||
 | 
				import sys | 
			
		||||
 | 
				import curses | 
			
		||||
 | 
				import termios | 
			
		||||
 | 
				import tty | 
			
		||||
 | 
				import socket | 
			
		||||
 | 
				import signal | 
			
		||||
 | 
				import getpass | 
			
		||||
 | 
				import platform | 
			
		||||
 | 
				import sqlite3 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				KEYS_ENTER = (curses.KEY_ENTER, ord('\n'), ord('\r')) | 
			
		||||
 | 
				KEYS_UP = (curses.KEY_UP, ord('k')) | 
			
		||||
 | 
				KEYS_DOWN = (curses.KEY_DOWN, ord('j')) | 
			
		||||
 | 
				KEYS_SELECT = (curses.KEY_RIGHT, ord(' ')) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# 数据库文件 | 
			
		||||
 | 
				gSqlite3File = "/usr/local/jumpserver/jumpserver.db" | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# ssh_private_path ssh私钥路径 | 
			
		||||
 | 
				system_type = platform.system() | 
			
		||||
 | 
				if system_type == "Darwin": | 
			
		||||
 | 
				    ssh_private_path = "/Users/%s/.ssh/id_rsa" | 
			
		||||
 | 
				elif system_type == "Linux": | 
			
		||||
 | 
				    ssh_private_path = "/home/%s/.ssh/id_rsa" | 
			
		||||
 | 
				else: | 
			
		||||
 | 
				    exit(1) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				class Picker(object): | 
			
		||||
 | 
				    def __init__(self, options, title=None, indicator='*', default_index=0, multiselect=False, multi_select=False, | 
			
		||||
 | 
				                 min_selection_count=0, options_map_func=None): | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if len(options) == 0: | 
			
		||||
 | 
				            raise ValueError('options should not be an empty list') | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        self.options = options | 
			
		||||
 | 
				        self.title = title | 
			
		||||
 | 
				        self.indicator = indicator | 
			
		||||
 | 
				        self.multiselect = multiselect or multi_select | 
			
		||||
 | 
				        self.min_selection_count = min_selection_count | 
			
		||||
 | 
				        self.options_map_func = options_map_func | 
			
		||||
 | 
				        self.all_selected = [] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if default_index >= len(options): | 
			
		||||
 | 
				            raise ValueError('default_index should be less than the length of options') | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if multiselect and min_selection_count > len(options): | 
			
		||||
 | 
				            raise ValueError( | 
			
		||||
 | 
				                'min_selection_count is bigger than the available options, you will not be able to make any selection') | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if options_map_func is not None and not callable(options_map_func): | 
			
		||||
 | 
				            raise ValueError('options_map_func must be a callable function') | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        self.index = default_index | 
			
		||||
 | 
				        self.custom_handlers = {} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def register_custom_handler(self, key, func): | 
			
		||||
 | 
				        self.custom_handlers[key] = func | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def move_up(self): | 
			
		||||
 | 
				        self.index -= 1 | 
			
		||||
 | 
				        if self.index < 0: | 
			
		||||
 | 
				            self.index = len(self.options) - 1 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def move_down(self): | 
			
		||||
 | 
				        self.index += 1 | 
			
		||||
 | 
				        if self.index >= len(self.options): | 
			
		||||
 | 
				            self.index = 0 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def mark_index(self): | 
			
		||||
 | 
				        if self.multiselect: | 
			
		||||
 | 
				            if self.index in self.all_selected: | 
			
		||||
 | 
				                self.all_selected.remove(self.index) | 
			
		||||
 | 
				            else: | 
			
		||||
 | 
				                self.all_selected.append(self.index) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def get_selected(self): | 
			
		||||
 | 
				        """return the current selected option as a tuple: (option, index) | 
			
		||||
 | 
				           or as a list of tuples (in case multiselect==True) | 
			
		||||
 | 
				        """ | 
			
		||||
 | 
				        if self.multiselect: | 
			
		||||
 | 
				            return_tuples = [] | 
			
		||||
 | 
				            for selected in self.all_selected: | 
			
		||||
 | 
				                return_tuples.append((self.options[selected], selected)) | 
			
		||||
 | 
				            return return_tuples | 
			
		||||
 | 
				        else: | 
			
		||||
 | 
				            return self.options[self.index], self.index | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def get_title_lines(self): | 
			
		||||
 | 
				        if self.title: | 
			
		||||
 | 
				            return self.title.split('\n') + [''] | 
			
		||||
 | 
				        return [] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def get_option_lines(self): | 
			
		||||
 | 
				        lines = [] | 
			
		||||
 | 
				        for index, option in enumerate(self.options): | 
			
		||||
 | 
				            # pass the option through the options map of one was passed in | 
			
		||||
 | 
				            if self.options_map_func: | 
			
		||||
 | 
				                option = self.options_map_func(option) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            if index == self.index: | 
			
		||||
 | 
				                prefix = self.indicator | 
			
		||||
 | 
				            else: | 
			
		||||
 | 
				                prefix = len(self.indicator) * ' ' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            if self.multiselect and index in self.all_selected: | 
			
		||||
 | 
				                format = curses.color_pair(1) | 
			
		||||
 | 
				                line = ('{0} {1}'.format(prefix, option), format) | 
			
		||||
 | 
				            else: | 
			
		||||
 | 
				                line = '{0} {1}'.format(prefix, option) | 
			
		||||
 | 
				            lines.append(line) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        return lines | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def get_lines(self): | 
			
		||||
 | 
				        title_lines = self.get_title_lines() | 
			
		||||
 | 
				        option_lines = self.get_option_lines() | 
			
		||||
 | 
				        lines = title_lines + option_lines | 
			
		||||
 | 
				        current_line = self.index + len(title_lines) + 1 | 
			
		||||
 | 
				        return lines, current_line | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def draw(self): | 
			
		||||
 | 
				        """draw the curses ui on the screen, handle scroll if needed""" | 
			
		||||
 | 
				        self.screen.clear() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        x, y = 1, 1  # start point | 
			
		||||
 | 
				        max_y, max_x = self.screen.getmaxyx() | 
			
		||||
 | 
				        max_rows = max_y - y  # the max rows we can draw | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        lines, current_line = self.get_lines() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        # calculate how many lines we should scroll, relative to the top | 
			
		||||
 | 
				        scroll_top = getattr(self, 'scroll_top', 0) | 
			
		||||
 | 
				        if current_line <= scroll_top: | 
			
		||||
 | 
				            scroll_top = 0 | 
			
		||||
 | 
				        elif current_line - scroll_top > max_rows: | 
			
		||||
 | 
				            scroll_top = current_line - max_rows | 
			
		||||
 | 
				        self.scroll_top = scroll_top | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        lines_to_draw = lines[scroll_top:scroll_top + max_rows] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        for line in lines_to_draw: | 
			
		||||
 | 
				            if type(line) is tuple: | 
			
		||||
 | 
				                self.screen.addnstr(y, x, line[0], max_x - 2, line[1]) | 
			
		||||
 | 
				            else: | 
			
		||||
 | 
				                self.screen.addnstr(y, x, line, max_x - 2) | 
			
		||||
 | 
				            y += 1 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        self.screen.refresh() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def run_loop(self): | 
			
		||||
 | 
				        while True: | 
			
		||||
 | 
				            self.draw() | 
			
		||||
 | 
				            c = self.screen.getch() | 
			
		||||
 | 
				            if c in KEYS_UP: | 
			
		||||
 | 
				                self.move_up() | 
			
		||||
 | 
				            elif c in KEYS_DOWN: | 
			
		||||
 | 
				                self.move_down() | 
			
		||||
 | 
				            elif c in KEYS_ENTER: | 
			
		||||
 | 
				                if self.multiselect and len(self.all_selected) < self.min_selection_count: | 
			
		||||
 | 
				                    continue | 
			
		||||
 | 
				                return self.get_selected() | 
			
		||||
 | 
				            elif c in KEYS_SELECT and self.multiselect: | 
			
		||||
 | 
				                self.mark_index() | 
			
		||||
 | 
				            elif c in self.custom_handlers: | 
			
		||||
 | 
				                ret = self.custom_handlers[c](self) | 
			
		||||
 | 
				                if ret: | 
			
		||||
 | 
				                    return ret | 
			
		||||
 | 
				            elif c == ord('q'): | 
			
		||||
 | 
				                exit(0) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def config_curses(self): | 
			
		||||
 | 
				        try: | 
			
		||||
 | 
				            # use the default colors of the terminal | 
			
		||||
 | 
				            curses.use_default_colors() | 
			
		||||
 | 
				            # hide the cursor | 
			
		||||
 | 
				            curses.curs_set(0) | 
			
		||||
 | 
				            # add some color for multi_select | 
			
		||||
 | 
				            # @todo make colors configurable | 
			
		||||
 | 
				            curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE) | 
			
		||||
 | 
				        except: | 
			
		||||
 | 
				            # Curses failed to initialize color support, eg. when TERM=vt100 | 
			
		||||
 | 
				            curses.initscr() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def _start(self, screen): | 
			
		||||
 | 
				        self.screen = screen | 
			
		||||
 | 
				        self.config_curses() | 
			
		||||
 | 
				        return self.run_loop() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def start(self): | 
			
		||||
 | 
				        return curses.wrapper(self._start) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				def updateWindowHandler(signum): | 
			
		||||
 | 
				    if signum == signal.SIGWINCH: | 
			
		||||
 | 
				        width, height = os.get_terminal_size() | 
			
		||||
 | 
				        os.terminal_size((width, height)) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				def posix_shell(chan): | 
			
		||||
 | 
				    import select | 
			
		||||
 | 
				    oldtty = termios.tcgetattr(sys.stdin) | 
			
		||||
 | 
				    signal.signal(signal.SIGWINCH, updateWindowHandler) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    try: | 
			
		||||
 | 
				        tty.setraw(sys.stdin.fileno(), termios.TCIOFLUSH) | 
			
		||||
 | 
				        # tty.setcbreak(sys.stdin.fileno()) | 
			
		||||
 | 
				        chan.settimeout(0.0) | 
			
		||||
 | 
				        while True: | 
			
		||||
 | 
				            r, w, e = select.select([chan, sys.stdin], [], []) | 
			
		||||
 | 
				            if chan in r: | 
			
		||||
 | 
				                try: | 
			
		||||
 | 
				                    data = str(chan.recv(1024), encoding='utf-8') | 
			
		||||
 | 
				                    if len(data) == 0: | 
			
		||||
 | 
				                        break | 
			
		||||
 | 
				                    sys.stdout.write(data) | 
			
		||||
 | 
				                    sys.stdout.flush() | 
			
		||||
 | 
				                except socket.timeout: | 
			
		||||
 | 
				                    pass | 
			
		||||
 | 
				            if sys.stdin in r: | 
			
		||||
 | 
				                ch = sys.stdin.read(1) | 
			
		||||
 | 
				                if len(ch) == 0: | 
			
		||||
 | 
				                    break | 
			
		||||
 | 
				                chan.send(ch) | 
			
		||||
 | 
				    finally: | 
			
		||||
 | 
				        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				def NewTerminal(host, port, user): | 
			
		||||
 | 
				    # 建立ssh连接 | 
			
		||||
 | 
				    ssh = paramiko.SSHClient() | 
			
		||||
 | 
				    ssh.load_system_host_keys() | 
			
		||||
 | 
				    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    ssh.connect( | 
			
		||||
 | 
				        host, | 
			
		||||
 | 
				        port=port, | 
			
		||||
 | 
				        username=user, | 
			
		||||
 | 
				        key_filename=ssh_private_path % user, | 
			
		||||
 | 
				        compress=False) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    # 建立交互式shell连接 | 
			
		||||
 | 
				    width, height = os.get_terminal_size() | 
			
		||||
 | 
				    channel = ssh.invoke_shell("xterm-256color", width, height) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    # 建立交互式管道 | 
			
		||||
 | 
				    posix_shell(channel) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    # 关闭连接 | 
			
		||||
 | 
				    channel.close() | 
			
		||||
 | 
				    ssh.close() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# 初始化表 | 
			
		||||
 | 
				def connect_db(): | 
			
		||||
 | 
				    # 连接数据库 | 
			
		||||
 | 
				    conn = sqlite3.connect(gSqlite3File) | 
			
		||||
 | 
				    if conn == None: | 
			
		||||
 | 
				        print("sqlite3.connect " + gSqlite3File + "failed!") | 
			
		||||
 | 
				        exit(1) | 
			
		||||
 | 
				    return conn | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				def get_hosts(user): | 
			
		||||
 | 
				    db = connect_db() | 
			
		||||
 | 
				    hosts = db.execute("select name,ip,port from hosts where isdelete=0 and name in (select hostname from hostuser where username='%s') order by id" % user).fetchall() | 
			
		||||
 | 
				    resp = [] | 
			
		||||
 | 
				    for host in hosts: | 
			
		||||
 | 
				        resp.append("%s:%s:%s" % (host[0], host[1], host[2])) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if len(resp) == 0: | 
			
		||||
 | 
				        print(user + " no valid hosts") | 
			
		||||
 | 
				        exit(1) | 
			
		||||
 | 
				    return resp | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				def main(): | 
			
		||||
 | 
				    user = getpass.getuser() | 
			
		||||
 | 
				    print("current user: " + user) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    title = "ssh hosts select:" | 
			
		||||
 | 
				    menu = get_hosts(user) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    while True: | 
			
		||||
 | 
				        option, index = Picker(menu, title).start() | 
			
		||||
 | 
				        arr = option.split(":") | 
			
		||||
 | 
				        if len(arr) == 3: | 
			
		||||
 | 
				            host = arr[1] | 
			
		||||
 | 
				            port = int(arr[2]) | 
			
		||||
 | 
				            NewTerminal(host, port, user) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				if __name__ == '__main__': | 
			
		||||
 | 
				    main() | 
			
		||||
@ -0,0 +1 @@ | 
			
		|||||
 | 
				paramiko==2.7.1 | 
			
		||||
@ -0,0 +1,24 @@ | 
			
		|||||
 | 
				# -*- coding: UTF-8 -*- | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				from pick import pick | 
			
		||||
 | 
				import sshserver | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				title = 'Please choose your favorite programming language: ' | 
			
		||||
 | 
				# options = ['quit', 'Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell'] | 
			
		||||
 | 
				options = ['ckqas:192.168.1.44:22', 'ckqas129:192.168.1.129:22'] | 
			
		||||
 | 
				# option, index = pick(options, title) | 
			
		||||
 | 
				# print(option, index) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				if __name__ == '__main__': | 
			
		||||
 | 
				    while True: | 
			
		||||
 | 
				        option, index = pick(options, title) | 
			
		||||
 | 
				        if option == "quit": | 
			
		||||
 | 
				            exit(0) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        arr = option.split(":") | 
			
		||||
 | 
				        if len(arr) == 3: | 
			
		||||
 | 
				            host = arr[1] | 
			
		||||
 | 
				            port = int(arr[2]) | 
			
		||||
 | 
				            sshserver.NewTerminal(host, port, "daniel") | 
			
		||||
 | 
				
 | 
			
		||||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue